﻿using System;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Web;
using Yahoo.Yui.Compressor;

namespace JQuery.WebKit.WebCommon.Compression
{
    public class CssCompressorHandler : IHttpHandler
    {
        private const int DAYS_IN_CACHE = 30;

        /// <summary>
        /// Enables processing of HTTP Web requests by a custom 
        /// HttpHandler that implements the <see cref="T:System.Web.IHttpHandler"></see> interface.
        /// </summary>
        /// <param name="context">An <see cref="T:System.Web.HttpContext"></see> object that provides 
        /// references to the intrinsic server objects 
        /// (for example, Request, Response, Session, and Server) used to service HTTP requests.
        /// </param>
        public void ProcessRequest(HttpContext context)
        {
            string root = context.Request.Url.GetLeftPart(UriPartial.Authority);
            string hash = context.Request.QueryString["d"];
            string content = null;
            string[] pathList = null;
            string url = null;
            string localUrl = null;
            if (!string.IsNullOrEmpty(hash))
            {
                url = GetCachedUrl(hash);
                if (string.IsNullOrWhiteSpace(url))
                {
                    return;
                }
                if (context.Cache[url] == null)
                {
                    pathList = url.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
                    foreach (string path in pathList)
                    {
                        //url = GetCachedUrl(path);
                        if (string.IsNullOrEmpty(path))
                        {
                            continue;
                        }
                        localUrl = HttpUtility.UrlDecode(path);
                        // We only want to serve resource files for security reasons.
                        //if (script.ToUpperInvariant().Contains("RESOURCE.AXD"))
                        content += RetrieveScript(root.AppendRelativeUrl(localUrl)) + Environment.NewLine;
                    }
                    if (string.IsNullOrWhiteSpace(content))
                    {
                        return;
                    }
                    content = CssCompressor.Compress(content);
                    context.Cache.Insert(url, content, null, System.Web.Caching.Cache.NoAbsoluteExpiration, new TimeSpan(DAYS_IN_CACHE, 0, 0, 0));
                }
                else
                {
                    content = (string)context.Cache[url];
                }
            }

            if (!string.IsNullOrEmpty(content))
            {
                context.Response.Write(content);
                SetHeaders(content.GetHashCode(), context);
                Compress(context);
            }
        }

        private string GetCachedUrl(string hashCode)
        {
            return HttpContext.Current.Cache[hashCode] as string;
        }

        /// <summary>
        /// Retrieves the specified remote script using a WebClient.
        /// </summary>
        /// <param name="file">The remote URL</param>
        private static string RetrieveScript(string file)
        {
            string script = null;

            try
            {
                Uri url = new Uri(file, UriKind.Absolute);

                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
                request.Method = "GET";
                request.AutomaticDecompression = DecompressionMethods.GZip;

                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                {
                    script = reader.ReadToEnd();
                }
            }
            catch (System.Net.Sockets.SocketException)
            {
                // The remote site is currently down. Try again next time.
            }
            catch (UriFormatException)
            {
                // Only valid absolute URLs are accepted
            }

            return script;
        }

        /// <summary>
        /// This will make the browser and server keep the output
        /// in its cache and thereby improve performance.
        /// </summary>
        private static void SetHeaders(int hash, HttpContext context)
        {
            context.Response.ContentType = "text/css";
            context.Response.Cache.VaryByHeaders["Accept-Encoding"] = true;

            context.Response.Cache.SetExpires(DateTime.Now.ToUniversalTime().AddDays(DAYS_IN_CACHE));
            context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
            context.Response.Cache.SetMaxAge(new TimeSpan(DAYS_IN_CACHE, 0, 0, 0));
            context.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
            context.Response.Cache.SetETag("\"" + hash.ToString() + "\"");
        }

        #region Compression

        private const string GZIP = "gzip";
        private const string DEFLATE = "deflate";

        private static void Compress(HttpContext context)
        {
            if (context.Request.UserAgent != null && context.Request.UserAgent.Contains("MSIE 6"))
                return;

            if (IsEncodingAccepted(GZIP))
            {
                context.Response.Filter = new GZipStream(context.Response.Filter, CompressionMode.Compress);
                SetEncoding(GZIP);
            }
            else if (IsEncodingAccepted(DEFLATE))
            {
                context.Response.Filter = new DeflateStream(context.Response.Filter, CompressionMode.Compress);
                SetEncoding(DEFLATE);
            }
        }

        /// <summary>
        /// Checks the request headers to see if the specified
        /// encoding is accepted by the client.
        /// </summary>
        private static bool IsEncodingAccepted(string encoding)
        {
            return HttpContext.Current.Request.Headers["Accept-encoding"] != null && HttpContext.Current.Request.Headers["Accept-encoding"].Contains(encoding);
        }

        /// <summary>
        /// Adds the specified encoding to the response headers.
        /// </summary>
        /// <param name="encoding"></param>
        private static void SetEncoding(string encoding)
        {
            HttpContext.Current.Response.AppendHeader("Content-encoding", encoding);
        }

        #endregion

        /// <summary>
        /// Gets a value indicating whether another request can use the <see cref="T:System.Web.IHttpHandler"></see> instance.
        /// </summary>
        /// <value></value>
        /// <returns>true if the <see cref="T:System.Web.IHttpHandler"></see> instance is reusable; otherwise, false.</returns>
        public bool IsReusable
        {
            get { return false; }
        }

    }
}
